﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace AMM.CustomWinControls {
    public partial class DropDownControlEx : UserControl {

        public event EventHandler DropDownShowed;
        public event EventHandler DropDownClosed;

        const int COMBOBOXBUTTONWIDTH = 19;
        const int SUBMITBUTTONHEIGHT = 21;

        private DropDownContainer _dropDownContainer;
        private Control _dropDownItem;
        private Size _anchorSize = new Size(121, 21);
        private Rectangle _anchorClientBounds;
        private bool _closedWhileInControl;
        private bool _droppedDownEx;

        [Browsable(false)]
        public bool DroppedDownEx {
            get {
                return _droppedDownEx;
            }
            set {
                this._droppedDownEx = value;
                if (this._droppedDownEx) { this.ShowDropDownEx(); }
            }
        }
        [Browsable(false)]
        public object ValueEx { get; set; }

        public DockSideEnumEx DockSideEx { get; set; }

        [Browsable(false)]
        public Size AnchorSizeEx {
            get { return this._anchorSize; }
            set {
                this._anchorSize = value;
                this.Invalidate();
            }
        }

        [Browsable(false)]
        protected bool CanDropEx {
            get {
                if (this._dropDownContainer != null) { return false; }
                if (this._dropDownContainer == null && this._closedWhileInControl) {
                    _closedWhileInControl = false;
                    return _closedWhileInControl;
                }
                return !this._closedWhileInControl;
            }
        }

        [Browsable(false)]
        protected Rectangle AnchorClientBoundsEx {
            get { return _anchorClientBounds; }
        }

        [Browsable(false)]
        protected bool IsInDesignModeEx {
            get {
                bool isInDesignMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime || Debugger.IsAttached == true;
                if (!isInDesignMode) {
                    using (var process = Process.GetCurrentProcess()) {
                        return process.ProcessName.ToLowerInvariant().Contains("devenv");
                    }
                }
                return isInDesignMode;
            }
        }

        protected DropDownControlEx() {
            this.InitializeComponent();
            // three rows coding as below can prevent from being flickering when 
            // we need to redraw Control
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);
        }

        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            // draw combobox
            if (ComboBoxRenderer.IsSupported) {
                ComboBoxState state = this.GetComboBoxState();
                ComboBoxRenderer.DrawTextBox(e.Graphics, new Rectangle(new Point(0, 0), this._anchorSize), state);
                ComboBoxRenderer.DrawDropDownButton(e.Graphics,
                    new Rectangle(this._anchorSize.Width - COMBOBOXBUTTONWIDTH - 1, 1, COMBOBOXBUTTONWIDTH, this._anchorSize.Height - 2), state);
            }
            else {
                ControlPaint.DrawComboButton(e.Graphics,
                    new Rectangle(this._anchorSize.Width - COMBOBOXBUTTONWIDTH - 1, 1, COMBOBOXBUTTONWIDTH, this._anchorSize.Height - 2),
                    this.Enabled ? ButtonState.Normal : ButtonState.Inactive);
            }
            // draw text
            string text = this.ValueEx == null ? string.Empty : this.ValueEx.ToString();
            Size size = TextRenderer.MeasureText(text, this.Font);
            Rectangle rec = this.AnchorClientBoundsEx;
            rec.Y = (rec.Height - size.Height) / 2;
            TextRenderer.DrawText(e.Graphics, text, this.Font, rec, this.ForeColor, TextFormatFlags.WordEllipsis);

            if (this.Focused) {
                ControlPaint.DrawFocusRectangle(e.Graphics, this.AnchorClientBoundsEx);
            }
        }

        protected override void OnGotFocus(EventArgs e) {
            base.OnGotFocus(e);
            this.Invalidate();
        }

        protected override void OnLostFocus(EventArgs e) {
            base.OnLostFocus(e);
            this.Invalidate();
        }

        protected override void OnResize(EventArgs e) {
            base.OnResize(e);
            this._anchorSize.Width = this.Width;
            if (!this.IsInDesignModeEx) {
                this._anchorSize.Height = this.Height;
            }
            this._anchorClientBounds = new Rectangle(1, 1, this._anchorSize.Width - COMBOBOXBUTTONWIDTH, this._anchorSize.Height - 2);
            this.Invalidate();
        }

        protected override void OnMouseDown(MouseEventArgs e) {
            base.OnMouseDown(e);
            if (this._dropDownContainer == null) {
                this.ShowDropDownEx();
            }
            else {
                this.CloseDropDownEx();
            }
        }

        protected override void OnMouseUp(MouseEventArgs e) {
            base.OnMouseUp(e);
            this.Invalidate();
        }

        protected void InitializeDropDownEx(Control dropDownItem) {
            //if (this._dropDownItem != null) {
            //    throw new Exception("The drop down item has already been implemented!");
            //}
            if (this.Controls.Contains(dropDownItem)) {
                this.Controls.Remove(dropDownItem);
            }
            this._dropDownItem = dropDownItem;
            this._anchorClientBounds = new Rectangle(1, 1, this._anchorSize.Width - COMBOBOXBUTTONWIDTH, this._anchorSize.Height - 2);

            this.DockSideEx = DockSideEnumEx.Left;
            this.Size = this._anchorSize;
        }

        protected void ShowDropDownEx() {
            if (_dropDownItem == null) {
                throw new NotImplementedException(
                    "The drop down item has not been initialized! Use the InitializeDropDown() method to do so.");
            }
            if (!this.CanDropEx) { return; }
            this._dropDownContainer = new DropDownContainer(this._dropDownItem);
            this._dropDownContainer.Bounds = this.GetDropDownBounds();
            this._dropDownContainer.DropStateChange += new DropDownContainer.DropWindowArgs(_dropDownContainer_DropStateChange);
            this._dropDownContainer.FormClosed += new FormClosedEventHandler(_dropDownContainer_FormClosed);
            this._dropDownContainer.Show(this);

            if (this._dropDownItem.Width < this._dropDownContainer.Width) {
                this._dropDownItem.Width = this._dropDownContainer.Width - 2;
            }

            this.ParentForm.Move += new EventHandler(ParentForm_Move);
            if (this.DropDownShowed != null) {
                this.DropDownShowed(this, EventArgs.Empty);
            }
            this._droppedDownEx = true;
            this.Invalidate();
        }

        protected void CloseDropDownEx() {
            this._droppedDownEx = false;
            if (this._dropDownContainer != null) {
                this._dropDownContainer.Close();
            }
            if (this.DropDownClosed != null) {
                this.DropDownClosed(this, EventArgs.Empty);
            }
        }

        private void ParentForm_Move(object sender, EventArgs e) {
            this._dropDownContainer.Bounds = this.GetDropDownBounds();
        }

        private void _dropDownContainer_FormClosed(object sender, FormClosedEventArgs e) {
            if (!this._dropDownContainer.IsDisposed) {
                this._dropDownContainer.DropStateChange -= this._dropDownContainer_DropStateChange;
                this._dropDownContainer.FormClosed -= this._dropDownContainer_FormClosed;
                this._dropDownContainer.Dispose();
                this._dropDownContainer = null;
                this.ParentForm.Move -= this.ParentForm_Move;
            }
            this._closedWhileInControl = this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position);
            this.Invalidate();
        }

        private void _dropDownContainer_DropStateChange() {
            //this.CloseDropDown();
        }

        private Rectangle GetDropDownBounds() {
            Size inflatedDropSize = new Size(
                this._anchorSize.Width > this._dropDownItem.Width ? this._anchorSize.Width : this._dropDownItem.Width + 2,
                this._dropDownItem.Height + 2);
            Rectangle screenBounds = this.DockSideEx == DockSideEnumEx.Left
                ? new Rectangle(this.Parent.PointToScreen(new Point(this.Bounds.X, this.Bounds.Bottom - 1)), inflatedDropSize)
                : new Rectangle(this.Parent.PointToScreen(new Point(this.Bounds.Right - inflatedDropSize.Width, this.Bounds.Bottom - 1)), inflatedDropSize);
            Rectangle workingArea = Screen.GetWorkingArea(screenBounds);
            // make sure we're completely in the top-left working area
            if (screenBounds.X < workingArea.X) { screenBounds.X = workingArea.X; }
            if (screenBounds.Y < workingArea.Y) { screenBounds.Y = workingArea.Y; }
            // make sure we're not extended past the working area's right /bottom edge
            if (screenBounds.Right > workingArea.Right && workingArea.Width > screenBounds.Width) {
                screenBounds.X = workingArea.Right - screenBounds.Width;
            }
            if (screenBounds.Bottom > workingArea.Bottom && workingArea.Height > screenBounds.Height) {
                //screenBounds.Y = workingArea.Bottom - screenBounds.Height;
                screenBounds.Y -= (screenBounds.Height + SUBMITBUTTONHEIGHT);
            }

            return screenBounds;
        }

        private ComboBoxState GetComboBoxState() {
            if (this.Enabled) {
                if (this._dropDownContainer != null) {
                    return ComboBoxState.Pressed;
                }
                else {
                    return ComboBoxState.Normal;
                }
            }
            else {
                return ComboBoxState.Disabled;
            }
        }

        public enum DockSideEnumEx {
            Left,
            Right
        }
    }

    internal sealed class DropDownContainer : Form, IMessageFilter {

        private const int CS_DROPSHADOW = 0x20000;

        public delegate void DropWindowArgs();
        public event DropWindowArgs DropStateChange;
        private void OnDropStateChange() {
            if (this.DropStateChange != null) {
                this.DropStateChange();
            }
        }

        #region Implemented IMessageFilter interface

        public bool PreFilterMessage(ref Message m) {
            if (this.Visible && (Form.ActiveForm == null || !Form.ActiveForm.Equals(this))) {
                this.OnDropStateChange();
                this.Close();
            }
            return false;
        }

        #endregion

        public DropDownContainer(Control dropDownItem) {
            this.FormBorderStyle = FormBorderStyle.None;
            this.ShowInTaskbar = false;
            this.StartPosition = FormStartPosition.Manual;
            this.MinimumSize = new Size(1, 1);
            this.ClientSize = this.Size;

            dropDownItem.Location = new Point(1, 1);
            this.Controls.Add(dropDownItem);

            Application.AddMessageFilter(this);
        }

        protected override CreateParams CreateParams {
            get {
                CreateParams cp = base.CreateParams;
                cp.ClassStyle |= CS_DROPSHADOW;
                return cp;
            }
        }

        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            e.Graphics.DrawRectangle(Pens.Gray, new Rectangle(0, 0, this.Width - 1, this.Height - 1));
        }

        protected override void OnClosing(CancelEventArgs e) {
            Application.RemoveMessageFilter(this);
            // prevent the control from being disposed
            this.Controls.RemoveAt(0);
            base.OnClosing(e);
        }
    }
}

